package mylab.starters.mybatis.interceptor.impl.foreignKey.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import mylab.base.Identifiable;
import mylab.base.model.entity.Entity;
import mylab.starters.mybatis.interceptor.impl.foreignKey.*;
import mylab.starters.mybatis.utils.MpUtils;
import mylab.utils.common.ReflectUtil;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;

import jakarta.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @Description:
 * @author: Sylar
 * @date: 2021年10月12日 11:15 下午
 */
@Slf4j
@SuppressWarnings("rawtypes")
public class ForeignKeyServiceImpl implements ForeignKeyService, ApplicationRunner {

    final List<String> packages;

    /**
     * 注册表
     */
    final List<RegisterItem> register = Lists.newArrayList();

    /**
     * 主/从 表对应关系
     * key   -- 主表类型
     * value -- 从表列表
     */
    Map<Class<?>, List<RegisterDetail>> map = Maps.newHashMap();

    public ForeignKeyServiceImpl(List<String> packages) {
        this.packages = packages;
    }

    @PostConstruct
    void init() {
        log.info("Init ForeignKeyServiceImpl");
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        register();
    }

    @Override
    public void register() {
        long start = System.currentTimeMillis();

        //查找所有实体类,并登记至 register
        List<Class<? extends Entity>> allEntityClass = MpUtils.findAllEntityClass(packages, Entity.class);
        allEntityClass.forEach(clazz -> {
            //查找包含@ForeignKey注解标记的字段列表
            List<Field> fields = ReflectUtil.getAllFieldByAnnotation(clazz, ForeignKey.class);
            if (fields.size() > 0) {
                //筛选字段出有 @ForeignKey 标记的实体类
                fields.forEach(field -> register.add(newKeyRelation(clazz, field)));
            }
        });

        //从 register 构造便于根据PK查找的 map
        register.forEach(item -> {
            Class<? extends Entity> pkEntityClass = item.getPkClass();
            if (!map.containsKey(pkEntityClass)) {
                map.put(pkEntityClass, Lists.newArrayList());
            }

            List<RegisterDetail> list = map.get(pkEntityClass);
            list.add(new RegisterDetail(pkEntityClass, item.getFkClass(), item.getFkProperty()));
        });

        long end = System.currentTimeMillis();
        long time = (end - start);
        log.debug("扫描所有实体完成，耗时 {} 秒", time / 1000f);

        printRegister();
    }

    @Override
    public void checkOnDelete(Class<? extends Entity> pkEntityClass, Object pkValue) throws ForeignKeyException {
        boolean hasRelation = map.containsKey(pkEntityClass);
        if (!hasRelation) {
            //注册表中没有任何引用关系
            return;
        }

        SqlSessionTemplate sqlSessionTemplate = getSqlSessionTemplate();

        //从注册表获取与主表有引用关系的所有从表
        List<RegisterDetail> list = map.get(pkEntityClass);
        for (RegisterDetail detail : list) {
            //遍历从表
            List<Entity> result = detail.findByFK(sqlSessionTemplate, pkValue);
            if (result.size() > 0) {
                String fkValues = Joiner.on(",").join(result.stream().map(Identifiable::getId).collect(Collectors.toList()));
                String error = StrUtil.format(
                        "存在外键引用，不能删除。\r\n主键表={} 主键表{}={} 引用表={} 引用表外键字段={} 引用表有外键的记录{}={}",
                        detail.getPkTable().getTableName(),
                        detail.getPkTable().getKeyColumn(),
                        pkValue,
                        detail.getFkTable().getTableName(),
                        detail.getFkColumn(),
                        detail.getFkTable().getKeyColumn(),
                        fkValues);
                throw new ForeignKeyException(error);
            }
        }
    }


    SqlSessionTemplate getSqlSessionTemplate() {
        return SpringUtil.getBean(SqlSessionTemplate.class);
    }


    RegisterItem newKeyRelation(Class<? extends Entity> fkEntityClass, Field fkField) {
        ForeignKey foreignKey = fkField.getAnnotation(ForeignKey.class);
        Class<? extends Entity> pkEntityClass = foreignKey.value();
        return new RegisterItem(pkEntityClass, fkEntityClass, foreignKey.PK(), fkField.getName());
    }

    void printRegister() {
        log.debug("######## ForeignKeyRegister printRegister START ########");
        register.forEach(item -> log.debug(item.toString()));
        log.debug("######## ForeignKeyRegister printRegister END   ########\r\n");
    }

}
